<?php
/**
 * Вспомогательный класс для работы с базой данных
 * для управления деревьями по алгоритму Nested Sets
 *
 * @category   X
 * @package    Arm_Db
 * @subpackage Arm_Db_Tree
 * @author     nrnwest@gmail.com
 * @programing   Стешенко Александр (lcfsoft@gmail.com)
 * @version    0.1
 */
class Arm_Db_Tree_Nested_Sql_Pdo_Mysql extends Arm_Db_Sql_Mysql
{

    /**
     * Объект с параметрами
     *
     * @var Arm_Db_Tree_Nested_Params
     */
    protected $_params = null;

    /**
     * Имя таблицы с деревьями
     *
     * @var string
     */
    protected $_tableName;

    /**
     * Имя файла, содержащего базовый шаблон для структурной таблицы дерева
     *
     * @var string
     */
    protected $_createTreeSqlTemplateFileName = 'tree.sql';

    /**
     * Установка имени стурктурной таблицы
     *
     * @param string $tableName
     */
    public function setTableName($tableName)
    {
        $this->_tableName = $tableName;
    }

    /**
     * Установка параметров.
     *
     * @param array $params
     */
    public function setParams($params)
    {
        $this->_params = $params;
    }

    /**
     * Получение имени таблицы
     *
     * @return string
     */
    protected function _getTableName()
    {
        return $this->_tableName;
    }

    /**
     * Получение идентификатора активного дерева
     *
     * @return int
     */
    protected function _getTreeId()
    {
        return $this->_params->getTreeId();
    }

    /**
     * Получение поля структурной таблицы
     * из объекта парметров
     *
     * @param string $column
     * @return string алиас
     */
    protected function _getColumn($column)
    {
        return $this->_params->getColumn($column);
    }

    /**
     * Получение алиаса для поля структурной таблицы
     * из объекта парметров
     *
     * @param string $column
     * @return string алиас
     */
    protected function _getAlias($column)
    {
        return $this->_params->getColumnAlias($column);
    }

    /**
     * Создание структурной таблицы на основе переданных параметров
     *
     * @param array $params
     */
    public function createTree($params = array())
    {
        // Получение шаблона запроса на создание таблицы
        $sqlTemplate = $this->_getCreateTreeSqlTemplate();
        // Подстановка значений параметров в шаблон
        $sql = $this->_prepareCreateTreeSql(
        $sqlTemplate, $params);
        try {
            // Запрос на создание таблицы
            $this->getDb()->query(
            $sql);
        } catch (Exception $exception) {
            throw new Arm_Db_Tree_Nested_Exception('Ошибка создания структурной' .
             ' таблицы: ' . $exception->getMessage());
        }
    }

    /**
     * Возвращает sql шаблон для создания структурной таблицы
     *
     * @return string
     */
    protected function _getCreateTreeSqlTemplate()
    {
        // файл находится в той же директории что и класс враппера
        $sqlTemplateDirectory = dirname(__FILE__);
        $filePath = $sqlTemplateDirectory . '\\' . $this->_createTreeSqlTemplateFileName;
        if (! is_readable($filePath)) {
            throw new Arm_Db_Tree_Nested_Exception('Ошибка создания таблицы, ' .
             'недоступен базовый шаблон создания структурной таблицы');
        }
        $sql = file_get_contents($filePath);
        return $sql;
    }

    /**
     * Подготавливает шаблон запроса на создание таблицы
     * путем подставления значения переменных шаблона
     *
     * @param string $sqlTemplate
     * @param array $params
     * @return string итоговый sql запрос
     */
    protected function _prepareCreateTreeSql($sqlTemplate, $params)
    {
        $resultParams = array();
        foreach ($params as $key => $identifier) {
            $resultParams[':' . $key] = $this->getDb()->quoteIdentifier(
            $identifier);
        }
        $sql = str_replace(array_keys($resultParams), $resultParams, $sqlTemplate);
        
        return $sql;
    }

    /**
     * Функция преобразующая объект ОРМ в массив
     *
     * @param Zend_Db_Select $select
     * @return array
     */
    public function fetchRow(Zend_Db_Select $select)
    {
        return $this->getDb()->fetchRow($select, array(), Zend_Db::FETCH_ASSOC);
    }

    /**
     * Функция преобразующая объект ОРМ в массив массивов
     *
     * @param Zend_Db_Select $select
     * @return array
     */
    public function fetchAll(Zend_Db_Select $select)
    {
        return $this->getDb()->fetchAll($select, array(), Zend_Db::FETCH_ASSOC);
    }

    /**
     * Функция использования транзакции
     *
     * Для врапперов СУБД не поддерживающих транзакции
     * можно использовать блокировку таблиц
     *
     */
    public function beginTransaction()
    {
        $this->getDb()->beginTransaction();
    }

    /**
     * Функция завершения транзакции
     *
     * Для врапперов СУБД не поддерживающих транзакции
     * можно использовать блокировку таблиц
     *
     */
    public function commitTransaction()
    {
        $this->getDb()->commit();
    }

    /**
     * Получение информации об узле
     *
     * @param int $nodeId идентификатор узла
     * @param int $treeId идентификатор дерева
     * @return Zend_Db_Select
     */
    public function getNode($nodeId, $treeId = null)
    {
        if ($treeId === null) {
            $treeId = $this->_getTreeId();
        }
        // Подгатавливаем объект Zend_Db_Select
        $select = $this->_getBaseSelectFrom()->where(
        $this->_getColumn('id') . ' = ?', $nodeId)->where(
        $this->_getColumn('tree_id') . ' = ?', $treeId);
        
        return $select;
    }

    /**
     * Получение информации об узле по его левому ключу
     *
     * @param int $leftKey левый ключ узла
     * @param int $treeId идентификатор дерева
     * @return Zend_Db_Select
     */
    public function getNodeByLeftKey($leftKey, $treeId = null)
    {
        if ($treeId === null) {
            $treeId = $this->_getTreeId();
        }
        // Подгатавливаем объект Zend_Db_Select
        $select = $this->_getBaseSelectFrom()->where(
        $this->_getColumn('left_key') . ' = ?', $leftKey)->where(
        $this->_getColumn('tree_id') . ' = ?', $treeId);
        
        return $select;
    }

    /**
     * Получение минимального левого ключа в дереве
     *
     * @param int $treeId идентификатор дерева
     * @return int минимальный левый ключ
     */
    public function getMinLeftKey($treeId = null)
    {
        if ($treeId === null) {
            $treeId = $this->_getTreeId();
        }
        $minLeftKey = $this->getDb()->fetchOne(
        $this->getDb()->select()->from($this->_getTableName(),
        new Zend_Db_Expr('MIN(' . $this->_getColumn('left_key')) . ') AS minLeftKey')->where(
        $this->_getColumn('tree_id') . ' = ?', $treeId));
        if (! $minLeftKey) {
            $minLeftKey = 1;
        }
        
        return $minLeftKey;
    }

    /**
     * Получение максимального Левого ключа
     *
     * @param int $treeId идентификатор дерева
     * @return int максимальный правый ключ
     */
    public function getMaxLeftKey($treeId = null)
    {
        if ($treeId === null) {
            $treeId = $this->_getTreeId();
        }
        // Вычисляем максимальный правый ключ
        $maxRightKey = $this->getDb()->fetchOne(
        $this->getDb()->select()->from($this->_getTableName(),
        new Zend_Db_Expr('MAX(' . $this->_getColumn('left_key')) . ') AS maxLeftKey')->where(
        $this->_getColumn('tree_id') . ' = ?', $treeId));
        if (! $maxRightKey) {
            $maxRightKey = 0;
        }
        
        return $maxRightKey;
    }
    
    /**
     * Получение максимального правого ключа в дереве
     *
     * @param int $treeId идентификатор дерева
     * @return int максимальный правый ключ
     */
    public function getMaxRightKey($treeId = null)
    {
        if ($treeId === null) {
            $treeId = $this->_getTreeId();
        }
        // Вычисляем максимальный правый ключ
        $maxRightKey = $this->getDb()->fetchOne(
        $this->getDb()->select()->from($this->_getTableName(),
        new Zend_Db_Expr('MAX(' . $this->_getColumn('right_key')) . ') AS maxRightKey')->where(
        $this->_getColumn('tree_id') . ' = ?', $treeId));
        if (! $maxRightKey) {
            $maxRightKey = 0;
        }
        
        return $maxRightKey;
    }

    /**
     * Добавление записи в структурную таблицу
     *
     * @param int $leftKey левый ключ
     * @param int $rightKey правый ключ
     * @param int $level уровень вложенности
     * @param int $treeId идентификатор дерева
     * @return int идентификатор добавленного элемента
     */
    public function insertNode($leftKey, $rightKey, $level, $treeId = null)
    {
        if ($treeId === null) {
            $treeId = $this->_getTreeId();
        }
        $this->getDb()->insert($this->_getTableName(),
        array($this->_getColumn('left_key') => $leftKey ,
        $this->_getColumn('right_key') => $rightKey ,
        $this->_getColumn('level') => $level , $this->_getColumn('tree_id') => $treeId));
        $id = $this->getDb()->lastInsertId($this->_getTableName());
        return $id;
    }

    /**
     * Вставка нескольких новых узлов
     * элементы входного массива должны быть массивами
     * и содержать следующие параметры:
     *
     * tree_id   - идентификатор дерева
     * left_key  - левый ключ
     * right_key - правый ключ
     * level     - уровень вложенности элемента
     *
     * @param array $insertData
     * @return true
     */
   public function insertNodes(&$insertData)
    {
    
   
         $colums = array(
         $this->_getColumn('id'),
         $this->_getColumn('left_key'), 
         $this->_getColumn('right_key'),
         $this->_getColumn('level'),
         $this->_getColumn('tree_id')
         );
         
         $select = $this->_db->select();
         $select->from($this->_getTableName(), 'id');
         $select->order('id DESC');
         $id = $this->_db->fetchOne($select);
         $idColumn = $this->_getColumn('id');

         foreach ($insertData as $k => $node) {
            $id++;
            $insertData[$k][$idColumn] = $id;
        }
         // запишем одним запросом
         $this->_insertMulti($this->_getTableName(), $colums, $insertData, $this->getDb());
         
        
               
        return true;
    }
    
    
    /**
     * Обновление дерева для вставки узла (узлов)
     *
     * Если вставка узла происходит в подчинение другому узлу, то
     * параметр $brother следует установить в true
     * Если происходит вставка смежного узла, то
     * параметр $brother следует установить в false
     *
     * @param int $key правый или левый ключ, в зависимости от места вставки
     * @param boolean $brother смежный узел / подчиненный
     * @param boolean $nodesCount количество вставляемых узлов
     * @param int $treeId идентификатор дерева
     */
    public function createUpdate($key, $brother = false, $nodesCount = 1, $treeId = null)
    {
       
        if ($treeId === null) {
            $treeId = $this->_getTreeId();
        }

     
        /*
         * Выполняем запрос на обновление таблиц.
         * В зависимости от значения параметра $brother будут использованы
         * разные операнды сравнения.
         */
        $bind =  array(
        $this->_getColumn('right_key') => new Zend_Db_Expr($this->_getColumn('right_key') .
         ' + ' . ($nodesCount * 2) . '') ,
        
        $this->_getColumn('left_key') => new Zend_Db_Expr('IF(' . $this->_getColumn(
        'left_key') . ($brother ? ' >= ' : ' > ') . $key . ', ' . $this->_getColumn(
        'left_key') . ' + ' . ($nodesCount * 2) . ', ' . $this->_getColumn('left_key') . ')')
        );
        
        $where =  array(
        new Zend_Db_Expr($this->_getColumn('right_key') . ($brother ? ' > ' : ' >= ') . $key) ,
        new Zend_Db_Expr($this->_getColumn('tree_id') . ' = ' . $treeId));

        
        $this->getDb()->update($this->_getTableName(), $bind, $where);
      
    }

    /**
     * Метод валидации дерева по принципу:
     * Левый ключ ВСЕГДА меньше правого
     *
     * @param int $treeId идентификатор дерева
     * @return boolean
     */
    public function validateLeftRightKeys($treeId = null)
    {
        if ($treeId === null) {
            $treeId = $this->_getTreeId();
        }
        
        $select = $this->getDb()->select()->from($this->_getTableName())->where(
        $this->_getColumn('tree_id') . ' = ?', $treeId)->where(
        $this->_getColumn('left_key') . '>=' . $this->_getColumn('right_key'));
        
        $result = $this->getDb()->fetchRow($select);
        
        if ($result) {
            throw new Arm_Db_Tree_Nested_Exception(Arm_Translate::_('Ошибка при проверки "левый ключ всегда больше правого" Данные %s', print_r($result, true)));
            // В выборке были записи, возвращаем ложь
            return false;
        }
        
        return true;
    }

    /**
     * Метод валидации дерева по принципу:
     * а) Наименьший левый ключ ВСЕГДА равен 1
     * б) Наибольший правый ключ ВСЕГДА равен двойному числу узлов
     *
     * @param int $treeId идентификатор дерева
     * @return boolean
     */
    public function validateMaxMinKeys($treeId = null)
    {
        if ($treeId === null) {
            $treeId = $this->_getTreeId();
        }
        
        $select = $this->getDb()->select()->from($this->_getTableName(),
        array(
        'nodesCount' => new Zend_Db_Expr('COUNT(' . $this->_getColumn('id') . ')') ,
        'minLeftKey' => new Zend_Db_Expr('MIN(' . $this->_getColumn('left_key') . ')') ,
        'maxRightKey' => new Zend_Db_Expr('MAX(' . $this->_getColumn('right_key') . ')')))->where(
        $this->_getColumn('tree_id') . ' = ?', $treeId);
        
        $result = $this->getDb()->fetchRow($select);
        $maxRightKey = $result['maxRightKey'];
        $minLeftKey = $result['minLeftKey'];
        $nodesCount = $result['nodesCount'];
        
        if ($minLeftKey != 1) {
            // минимальный левый ключ не равен единице, возвращаем ложь
             throw new Arm_Db_Tree_Nested_Exception(
             Arm_Translate::_('Ошибка при проверки минимальный левый ключ не равен единице, равен: %s', $minLeftKey)
             );
            return false;
        }
        if ($maxRightKey != $nodesCount * 2) {
            throw new Arm_Db_Tree_Nested_Exception(
             Arm_Translate::_('Ошибка при проверке максимальный правый ключ не равен двойному количеству узлов, равен: %s', $maxRightKey)
             );
            // максимальный правый ключ не равен двойному количеству узлов
            // возвращаем ложь
            return false;
        }
        
        return true;
    }

    /**
     * Метод валидации дерева по принципу:
     * а) Разница между правым и левым ключом ВСЕГДА нечетное число
     * б) Если уровень узла нечетное число то тогда левый ключ ВСЕГДА
     *    нечетное число, то же самое и для четных чисел;
     *
     * @param int $treeId идентификатор дерева
     * @return boolean
     */
    public function validateKeysDiff($treeId = null)
    {
        if ($treeId === null) {
            $treeId = $this->_getTreeId();
        }
        
        $select = $this->getDb()->select()->from($this->_getTableName(),
        array($this->_getColumn('id') ,
        'diff' => new Zend_Db_Expr('MOD((' . $this->_getColumn('right_key') . ' - ' . $this->_getColumn(
        'left_key') . '), 2)')))->having('diff = ?', 0)->where(
        $this->_getColumn('tree_id') . ' = ?', $treeId);
        
        $result = $this->getDb()->fetchRow($select);
        if ($result) {
              throw new Arm_Db_Tree_Nested_Exception(Arm_Translate::_('Ошибка при проверки "validateKeysDiff №1" Данные %s', print_r($result, true)));
          
            // В выборке были записи, возвращаем ложь
            return false;
        }
        
        $select = $this->getDb()->select()->from($this->_getTableName(),
        array($this->_getColumn('id') ,
        'diff' => new Zend_Db_Expr('MOD((' . $this->_getColumn('left_key') . ' - ' . $this->_getColumn(
        'level') . ' + 2), 2)')))->having('diff = ?', 1)->where(
        $this->_getColumn('tree_id') . ' = ?', $treeId);
        
        $result = $this->getDb()->fetchRow($select);
        if ($result) {
            // В выборке были записи, возвращаем ложь
             throw new Arm_Db_Tree_Nested_Exception(Arm_Translate::_('Ошибка при проверки "validateKeysDiff №1" Данные %s', print_r($result, true)));
          
            return false;
        }
        
        return true;
    }

    /**
     * Метод валидации дерева по принципу:
     * Ключи ВСЕГДА уникальны, вне зависимости от того правый он или левый
     *
     * @param int $treeId идентификатор дерева
     * @return boolean
     */
    public function validateUniqKeys($treeId = null)
    {
        if ($treeId === null) {
            $treeId = $this->_getTreeId();
        }
        
        $sql = Zend_Db_Select::SQL_SELECT . ' ' . $this->getDb()->quoteIdentifier(
        't1.' . $this->_getColumn('id')) . ', COUNT(' . $this->getDb()->quoteIdentifier(
        't1.' . $this->_getColumn('id')) . ') ' . Zend_Db_Select::SQL_AS . ' rep, ' . 'MAX(' .
         $this->getDb()->quoteIdentifier('t3.' . $this->_getColumn('right_key')) . ') ' .
         Zend_Db_Select::SQL_AS . ' maArm_right ' . Zend_Db_Select::SQL_FROM . ' ' . $this->getDb()->quoteIdentifier(
        $this->_getTableName()) . ' ' . Zend_Db_Select::SQL_AS . ' t1, ' . $this->getDb()->quoteIdentifier(
        $this->_getTableName()) . ' ' . Zend_Db_Select::SQL_AS . ' t2, ' . $this->getDb()->quoteIdentifier(
        $this->_getTableName()) . ' ' . Zend_Db_Select::SQL_AS . ' t3 ' . Zend_Db_Select::SQL_WHERE .
         ' ' . $this->getDb()->quoteIdentifier('t1.' . $this->_getColumn('left_key')) . ' <> ' .
         $this->getDb()->quoteIdentifier('t2.' . $this->_getColumn('left_key')) . ' ' . Zend_Db_Select::SQL_AND .
         ' ' . $this->getDb()->quoteIdentifier('t1.' . $this->_getColumn('left_key')) . ' <> ' .
         $this->getDb()->quoteIdentifier('t2.' . $this->_getColumn('right_key')) . ' ' . Zend_Db_Select::SQL_AND .
         ' ' . $this->getDb()->quoteIdentifier('t1.' . $this->_getColumn('right_key')) . ' <> ' .
         $this->getDb()->quoteIdentifier('t2.' . $this->_getColumn('left_key')) . ' ' . Zend_Db_Select::SQL_AND .
         ' ' . $this->getDb()->quoteIdentifier('t1.' . $this->_getColumn('right_key')) . ' <> ' .
         $this->getDb()->quoteIdentifier('t2.' . $this->_getColumn('right_key')) . ' ' . Zend_Db_Select::SQL_AND .
         ' ' . $this->getDb()->quoteIdentifier('t1.' . $this->_getColumn('tree_id')) . ' = ' .
         $this->getDb()->quote($treeId) . ' ' . Zend_Db_Select::SQL_AND . ' ' . $this->getDb()->quoteIdentifier(
        't2.' . $this->_getColumn('tree_id')) . ' = ' . $this->getDb()->quote($treeId) . ' ' .
         Zend_Db_Select::SQL_AND . ' ' . $this->getDb()->quoteIdentifier(
        't3.' . $this->_getColumn('tree_id')) . ' = ' . $this->getDb()->quote($treeId) . ' ' .
         Zend_Db_Select::SQL_GROUP_BY . $this->getDb()->quoteIdentifier(
        't1.' . $this->_getColumn('id')) . ' ' . Zend_Db_Select::SQL_HAVING . ' maArm_right <> SQRT(4 * rep + 1) + 1';
        
        $result = $this->getDb()->fetchRow($sql);
        if ($result) {
             throw new Arm_Db_Tree_Nested_Exception(Arm_Translate::_('Проверка уникальности ключей, ключи не уникальны Данные %s', print_r($result, true)));
          
            // В выборке были записи, возвращаем ложь
            return false;
        }
        
        return true;
    }

    /**
     * Полная проверка правильности дерева
     * Возвращает false если структура нарушена,
     * true - если все в порядке
     *
     * @param int $treeId идентификатор дерева
     * @return boolean
     */
    public function validateTree($treeId = null)
    {
        if ($treeId === null) {
            $treeId = $this->_getTreeId();
        }
        
        $check = $this->validateLeftRightKeys($treeId);
        $check &= $this->validateMaxMinKeys($treeId);
        $check &= $this->validateKeysDiff($treeId);
        $check &= $this->validateUniqKeys($treeId);
        
        return (boolean) $check;
    }

    /**
     * Удаление узла и вложенных, по левому и правому ключам
     *
     * @param int $leftKey левый ключ удаленного узла
     * @param int $righKey правый ключ удаленного узла
     * @param int $treeId идентификатор дерева
     */
    public function delete($leftKey, $rightKey, $treeId = null)
    {
        if ($treeId === null) {
            $treeId = $this->_getTreeId();
        }
        // Удаляем узел (ветку):
        $this->getDb()->delete(
        $this->_getTableName(),
        array(new Zend_Db_Expr($this->_getColumn('left_key') . ' >= ' . $leftKey) ,
        new Zend_Db_Expr($this->_getColumn('right_key') . ' <= ' . $rightKey) ,
        new Zend_Db_Expr($this->_getColumn('tree_id') . ' = ' . $treeId)));
    }

    /**
     * Удаление данных из связанных таблиц
     *
     * @param array $ids массив идентификаторов которые должны быть удалены
     */
    public function deleteCallback($ids)
    {
        $callbackDelete = $this->_params->getCallbackDelete();
        foreach ($callbackDelete as $tableName => $nodeIdentifier) {
            $this->getDb()->delete($tableName,
            new Zend_Db_Expr($this->getDb()->quoteIdentifier(
            $nodeIdentifier) . ' IN (' . implode(',', $ids) . ')'));
        }
    }

    /**
     * Обновление дерева после удаления узла
     *
     * @param int $leftKey левый ключ удаленного узла
     * @param int $righKey правый ключ удаленного узла
     * @param int $treeId идентификатор дерева
     */
    public function deleteUpdate($leftKey, $rightKey, $treeId = null)
    {
        if ($treeId === null) {
            $treeId = $this->_getTreeId();
        }
        // Обновляем ключи в оставшихся узлах
        $this->getDb()->update(
        $this->_getTableName(),
        array(
        $this->_getColumn('right_key') => new Zend_Db_Expr($this->_getColumn('right_key') .
         ' - (' . $rightKey . ' - ' . $leftKey . ' + 1)') ,
        
        $this->_getColumn('left_key') => new Zend_Db_Expr('IF(' . $this->_getColumn(
        'left_key') . ' > ' . $leftKey . ', ' . $this->_getColumn('left_key') . ' - (' . $rightKey .
         ' - ' . $leftKey . ' + 1 ), ' . $this->_getColumn('left_key') . ')')),
        array(new Zend_Db_Expr($this->_getColumn('right_key') . ' > ' . $rightKey) ,
        new Zend_Db_Expr($this->_getColumn('tree_id') . ' = ' . $treeId)));
    }

    /**
     * Удаление дерева
     *
     * @param int $treeId
     * @return boolean
     */
    public function deleteTree($treeId)
    {
        $treeId = $this->getDb()->quote($treeId, Zend_Db::INT_TYPE);
        $this->getDb()->delete($this->_getTableName(),
        $this->_getColumn('tree_id') . ' = ' . $treeId);
        
        return true;
    }

    /**
     * Удаление всех деревьев в таблице
     *
     * @return boolean
     */
    public function deleteTrees()
    {
        $this->getDb()->query('TRUNCATE TABLE ' . $this->_getTableName());
        
        return true;
    }

    /**
     * Полное удаление всех данных из всех связанных таблиц
     *
     */
    public function deleteCallbackTrees()
    {
        $callbackDeleteTables = array_keys($this->_params->getCallbackDelete());
        foreach ($callbackDeleteTables as $tableName) {
            $this->getDb()->query(
            'TRUNCATE TABLE ' . $this->getDb()->quoteIdentifier(
            $tableName));
        }
    }

    /**
     * Вспомагательный метод, возвращает заготовку Zend_Db_Select
     * для методов получения данных, для осуществления выборки
     * по алиасам, установленным в {@link Arm_Db_Tree_Nested_Params},
     * а не по именам колонок.
     *
     * @return Zend_Db_Select
     */
    protected function _getBaseSelectFrom()
    {
        $select = $this->getDb()->select()->from($this->_getTableName());
        return $select;
    }

    /**
     * Получение идентификаторов всех деревьев в таблице
     *
     * @return array
     */
    public function getAllTreesIds()
    {
        $select = $this->getDb()->select()->from($this->_getTableName(),
        array($this->_getAlias('tree_id') => $this->_getColumn('tree_id')))->distinct();
        return $this->getDb()->fetchCol($select);
    }

    /**
     * Получение дерева по идентификатору
     *
     * @param int $treeId
     * @return Zend_Db_Select
     */
    public function getTree($treeId)
    {
        if ($treeId === null) {
            $treeId = $this->_getTreeId();
        }
        
        $select = $this->_getBaseSelectFrom()->where(
        $this->_getColumn('tree_id') . ' = ?', $treeId)->order(
        $this->_getColumn('left_key'));
        
        return $select;
    }

    /**
     * Получение всех узлов встречающихся на пути к заданному.
     *
     * @param int $leftKey левый ключ узла
     * @param int $rightKey правый ключ узла
     * @param int $treeId идентификатор дерева
     * @return Zend_Db_Select
     */
    public function getPath($leftKey, $rightKey, $treeId = null)
    {
        if ($treeId === null) {
            $treeId = $this->_getTreeId();
        }
        
        $select = $this->_getBaseSelectFrom()->where(
        $this->_getColumn('left_key') . ' <= ' . $leftKey)->where(
        $this->_getColumn('right_key') . ' >= ' . $rightKey)->where(
        $this->_getColumn('tree_id') . ' = ?', $treeId)->order(
        $this->_getColumn('left_key'));
        
        return $select;
    }

    /**
     * Получение информации о родительском узле
     *
     * @param int $leftKey
     * @param int $rightKey
     * @param int $level
     * @param int $treeId
     * @return Zend_Db_Select
     */
    public function getParent($leftKey, $rightKey, $level, $treeId = null)
    {
        if ($treeId === null) {
            $treeId = $this->_getTreeId();
        }
        
        $select = $this->_getBaseSelectFrom()->where(
        $this->_getColumn('left_key') . ' <= ' . $leftKey)->where(
        $this->_getColumn('right_key') . ' >= ' . $rightKey)->where(
        $this->_getColumn('level') . ' = ' . ($level - 1))->where(
        $this->_getColumn('tree_id') . ' = ?', $treeId)->order(
        $this->_getColumn('left_key'));
        
        return $select;
    }

    /**
     * Получение всех узлов с уровнем вложенности 1
     *
     * @param int $treeId идентификатор дерева
     * @return Zend_Db_Select
     */
    public function getRootNodes($treeId = null)
    {
        if ($treeId === null) {
            $treeId = $this->_getTreeId();
        }
        
        $select = $this->_getBaseSelectFrom()->where($this->_getColumn('level') . ' = 1')->where(
        $this->_getColumn('tree_id') . ' = ?', $treeId)->order(
        $this->_getColumn('left_key'));
        return $select;
    }

    /**
     * Получение открытого дерева
     *
     * @param int $leftKey левый ключ
     * @param int $rightKey правый ключ
     * @param int $level уровень вложенности
     * @param int $treeId идентификатор дерева
     * @return Zend_Db_Select
     */
    public function getOpenTree($leftKey, $rightKey, $level, $treeId = null)
    {
        if ($treeId === null) {
            $treeId = $this->_getTreeId();
        }
        
        $parent = $this->getDb()->fetchRow($this->getParent($leftKey, $rightKey, $level, $treeId));
        if($parent === false or $parent === array()) {
            // возвращаем корневый узлы
            return $this->getRootNodes($treeId);
        }
     /*   $whereCondition = '(' . $this->_getColumn('left_key') . ' < ' . $rightKey . ' ' .
         Zend_Db_Select::SQL_AND . ' ' . $this->_getColumn('right_key') . ' > ' . $leftKey .
         ' ' . Zend_Db_Select::SQL_AND . ' ' . $this->_getColumn('level') . ' <= ' . ($level +
         1) . ')';
        
        $rootLevelWhereCondition = '(' . $this->_getColumn('level') . ' = 1' . ')';
        
        $select = $this->_getBaseSelectFrom()->where(
        new Zend_Db_Expr('(' . $whereCondition . Zend_Db_Select::SQL_OR . $rootLevelWhereCondition .
         ')'))->where($this->_getColumn('tree_id') . ' = ?', $treeId)->order(
        $this->_getColumn('left_key'));
        // добавляем все родительские узды
        $select->orwhere($this->_getColumn('level') . ' = 1');*/
        $select = $this->_getSelect();
        $select->from($this->_getTableName(), $this->_getColumn('id'));
        // получаем путь к элементу от вершины
        $select->where($this->_getColumn('right_key') .'>?', $leftKey);
        $select->where($this->_getColumn('left_key') .'<?', $rightKey);
        $select->where($this->_getColumn('tree_id') . ' = ?', $treeId);
         // уровень не больше чем +1 от текущего элемента
        $select->where('level <= ?', $level+1);
        $select->order($this->_getColumn('left_key'));
        $id1 = $this->getDb()->fetchCol($select);
        // получаем братние узлы на основе данных родителя
        $select = $this->getChildren($parent[$this->_getColumn('left_key')], $parent[$this->_getColumn('right_key')], $parent[$this->_getColumn('level')]);
        $id2 = $this->getDb()->fetchCol($select);
        // нужно получить все братние узлы родительксого узла 
        $parent2 = $this->getDb()->fetchRow($this->getParent($parent[$this->_getColumn('left_key')], $parent[$this->_getColumn('right_key')], $parent[$this->_getColumn('level')], $treeId));
        
        if($parent2 === array() or $parent2 === false  or $parent2['level'] === '1') {
            $id4 = array();
        } else {
        $select = $this->_getBaseSelectFrom();
        $select->where($this->_getColumn('left_key') .'>=?', $parent2[$this->_getColumn('left_key')]);
        $select->where($this->_getColumn('right_key') .'<=?', $parent2[$this->_getColumn('right_key')]);
        $select->where($this->_getColumn('tree_id') . ' = ?', $treeId);
        $id4 = $this->getDb()->fetchCol($select);
        }
       
        // добавим всех корневых родителей это узлы с уровнем 2!!!
        $select = $this->_getSelect();
        $select->from($this->_getTableName(), $this->_getColumn('id'));
        $select->where($this->_getColumn('level') . '=?', 2);
        $select->where($this->_getColumn('tree_id') . ' = ?', $treeId);
        $id3 = $this->getDb()->fetchCol($select);
        
        // все получаем все нужные узлы
        $id  = array_merge($id1, $id2, $id3, $id4);
       
      
        
        // добавим родителя
        $id[] = $parent[$this->_getColumn('id')];
        
        $id  = array_unique($id);
        $select = $this->_getBaseSelectFrom();
        $select->where($this->_getColumn('id') . ' in (?)', $id);
        $select->order($this->_getColumn('left_key'));
       
        
        return $select;
    }

    /**
     * Получение детей (только на уровень ниже)
     *
     * @param int $leftKey левый ключ
     * @param int $rightKey правый ключ
     * @param int $level уровень вложенности
     * @param int $treeId идентификатор дерева
     * @return Zend_Db_Select
     */
    public function getChildren($leftKey, $rightKey, $level, $treeId = null)
    {
        if ($treeId === null) {
            $treeId = $this->_getTreeId();
        }
        
        $select = $this->getChildrens($leftKey, $rightKey, $treeId)->where(
        $this->_getColumn('level') . ' = ' . ($level + 1));
        
        return $select;
    }

    /**
     * Получение всех детей
     *
     * @param int $leftKey левый ключ
     * @param int $rightKey правый ключ
     * @param int $treeId идентификатор дерева
     * @return Zend_Db_Select
     */
    public function getChildrens($leftKey, $rightKey, $treeId = null)
    {
        if ($treeId === null) {
            $treeId = $this->_getTreeId();
        }
        
        $select = $this->_getBaseSelectFrom()->where(
        $this->_getColumn('left_key') . ' > ' . $leftKey)->where(
        $this->_getColumn('right_key') . ' < ' . $rightKey)->where(
        $this->_getColumn('tree_id') . ' = ?', $this->_getTreeId())->order(
        $this->_getColumn('left_key'));
        
        return $select;
    }

    /**
     * Перемещение узла вверх по дереву, без изменения уровня и родителя
     *
     * @param int $leftKey левый ключ
     * @param int $rightKey правый ключ
     * @param int $level уровень вложенности
     * @param int $treeId идентификатор дерева
     * @return boolean
     */
    public function movingUp($leftKey, $rightKey, $level, $treeId = null)
    {
        if ($treeId === null) {
            $treeId = $this->_getTreeId();
        }
        
        // Определяем данные места перемещения - узла, за которым
        // будет располагаться перемещаемый узел
        $select = $this->getDb()->select()->from(
        $this->_getTableName(), $this->_getColumn('left_key'))->where(
        $this->_getColumn('right_key') . ' = ?', ($leftKey - 1))->where(
        $this->_getColumn('tree_id') . ' = ?', $treeId);
        $row = $this->getDb()->fetchRow($select);
        if ($row) {
            $near = $row[$this->_getColumn('left_key')] - 1;
        } else {
            // Нет места для перемещения
            return false;
        }
        
        return $this->moveNode($leftKey, $rightKey, $level, $level, $near, $treeId);
    }

    /**
     * Перемещение узла вниз по дереву, без изменения уровня и родителя
     *
     * @param int $leftKey левый ключ
     * @param int $rightKey правый ключ
     * @param int $level уровень вложенности
     * @param int $treeId идентификатор дерева
     * @return boolean
     */
    public function movingDown($leftKey, $rightKey, $level, $treeId = null)
    {
        if ($treeId === null) {
            $treeId = $this->_getTreeId();
        }
        
        // Определяем данные места перемещения - узла, за которым
        // будет располагаться перемещаемый узел
        $select = $this->getDb()->select()->from(
        $this->_getTableName(), $this->_getColumn('right_key'))->where(
        $this->_getColumn('left_key') . ' = ?', ($rightKey + 1))->where(
        $this->_getColumn('tree_id') . ' = ?', $treeId);
        $row = $this->getDb()->fetchRow($select);
        if ($row) {
            $near = $row[$this->_getColumn('right_key')];
        } else {
            // Нет места для перемещения
            return false;
        }
        
        return $this->moveNode($leftKey, $rightKey, $level, $level, $near, $treeId);
    }

    /**
     * Перемещение узла
     *
     * @param int $leftKey левый ключ перемещаемого узла
     * @param int $rightKey правый ключ перемещаемого узла
     * @param int $level уровень перемещаемого узла
     * @param int $levelNew уровень - куда перемещается узел
     * @param int $near "точка перемещения" будущий левый ключ перемещаемого узла, уменьшенный на единицу
     * @param int $treeId идентификатор дерева
     * @return boolean
     */
    public function moveNode($leftKey, $rightKey, $level, $levelNew, $near, $treeId = null)
    {
        if ($treeId === null) {
            $treeId = $this->_getTreeId();
        }
        
        if (($near >= $leftKey) && ($near <= $rightKey)) {
            // Проверка на перемещение в "самого себя"
            return false;
        }
        // Определяем смещение ключей перемещаемого узла
        $skewTree = $rightKey - $leftKey + 1;
        // Определяем смещение уровня
        $skewLevel = $levelNew - $level;
        
        if ($rightKey < $near) { // Если перемещаем вверх по дереву
            return $this->_moveNodeUp($leftKey, $rightKey,
            $skewTree, $skewLevel, $near, $treeId);
        } else { // Если перемещаем вниз по дереву
            return $this->_moveNodeDown($leftKey, $rightKey,
            $skewTree, $skewLevel, $near, $treeId);
        }
    }

    /**
     * Вспомогательный метод для перемещения узла вверх по дереву
     *
     * @param int $leftKey левый ключ перемещаемого узла
     * @param int $rightKey правый ключ перемещаемого узла
     * @param int $skewTree смещение ключей перемещаемого узл
     * @param int $skewLevel смещение уровня
     * @param int $near "точка перемещения" будущий левый ключ перемещаемого узла, уменьшенный на единицу
     * @param int $treeId идентификатор дерева
     * @return boolean
     */
    protected function _moveNodeUp($leftKey, $rightKey, $skewTree, $skewLevel, $near, $treeId)
    {
        // Определяем смещение ключей для дерева
        $skewEdit = $near - $leftKey + 1 - $skewTree;
        // Переносим узел и обновляем дерево
        $this->getDb()->update(
        $this->_getTableName(),
        array(
        $this->_getColumn('left_key') => new Zend_Db_Expr('IF(' . $this->_getColumn(
        'right_key') . ' <= ' . $rightKey . ', ' . $this->_getColumn('left_key') . ' + ' .
         $skewEdit . ', IF(' . $this->_getColumn('left_key') . ' > ' . $rightKey . ', ' .
         $this->_getColumn('left_key') . ' - ' . $skewTree . ', ' . $this->_getColumn(
        'left_key') . '))') ,
        
        $this->_getColumn('level') => new Zend_Db_Expr('IF(' . $this->_getColumn(
        'right_key') . ' <= ' . $rightKey . ', ' . $this->_getColumn('level') . ' + ' . $skewLevel .
         ', ' . $this->_getColumn('level') . ')') ,
        
        $this->_getColumn('right_key') => new Zend_Db_Expr('IF(' . $this->_getColumn(
        'right_key') . ' <= ' . $rightKey . ', ' . $this->_getColumn('right_key') . ' + ' .
         $skewEdit . ', IF(' . $this->_getColumn('right_key') . ' <= ' . $near . ', ' . $this->_getColumn(
        'right_key') . ' - ' . $skewTree . ', ' . $this->_getColumn('right_key') . '))')),
        array(new Zend_Db_Expr($this->_getColumn('right_key') . ' > ' . $leftKey) ,
        new Zend_Db_Expr($this->_getColumn('left_key') . ' <= ' . $near) ,
        new Zend_Db_Expr($this->_getColumn('tree_id') . ' = ' . $treeId)));
        return true;
    }

    /**
     * Вспомогательный метод для перемещения узла вниз по дереву
     *
     * @param int $leftKey левый ключ перемещаемого узла
     * @param int $rightKey правый ключ перемещаемого узла
     * @param int $skewTree смещение ключей перемещаемого узл
     * @param int $skewLevel смещение уровня
     * @param int $near "точка перемещения" будущий левый ключ перемещаемого узла,
     * 			  уменьшенный на единицу
     * @param int $treeId идентификатор дерева
     * @return boolean
     */
    protected function _moveNodeDown($leftKey, $rightKey, $skewTree, $skewLevel, $near, $treeId)
    {
        // Определяем смещение ключей для дерева
        $skewEdit = $near - $leftKey + 1;
        // Переносим узел и обновляем дерево
        $this->getDb()->update(
        $this->_getTableName(),
        array(
        $this->_getColumn('right_key') => new Zend_Db_Expr('IF(' . $this->_getColumn(
        'left_key') . ' >= ' . $leftKey . ', ' . $this->_getColumn('right_key') . ' + ' .
         $skewEdit . ', IF(' . $this->_getColumn('right_key') . ' < ' . $leftKey . ', ' .
         $this->_getColumn('right_key') . ' + ' . $skewTree . ', ' . $this->_getColumn(
        'right_key') . '))') ,
        
        $this->_getColumn('level') => new Zend_Db_Expr('IF(' . $this->_getColumn(
        'left_key') . ' >= ' . $leftKey . ', ' . $this->_getColumn('level') . ' + ' . $skewLevel .
         ', ' . $this->_getColumn('level') . ')') ,
        
        $this->_getColumn('left_key') => new Zend_Db_Expr('IF(' . $this->_getColumn(
        'left_key') . ' >= ' . $leftKey . ', ' . $this->_getColumn('left_key') . ' + ' . $skewEdit .
         ', IF(' . $this->_getColumn('left_key') . ' > ' . $near . ', ' . $this->_getColumn(
        'left_key') . ' + ' . $skewTree . ', ' . $this->_getColumn('left_key') . '))')),
        array(new Zend_Db_Expr($this->_getColumn('right_key') . ' > ' . $near) ,
        new Zend_Db_Expr($this->_getColumn('left_key') . ' < ' . $rightKey) ,
        new Zend_Db_Expr($this->_getColumn('tree_id') . ' = ' . $treeId)));
        return true;
    }

    /**
     * Перемещение рядом с другим узлом, выше
     *
     * @param int $leftKey левый ключ
     * @param int $rightKey правый ключ
     * @param int $level уровень вложенности
     * @param int $brotherLeftKey левый ключ узла к которому переносим
     * @param int $brotherLevel уровень вложенности узла к которому переносим
     * @param int $treeId идентификатор дерева
     * @return boolean
     */
    public function movingBrotherUp($leftKey, $rightKey, $level, $brotherLeftKey, $brotherLevel,
    $treeId = null)
    {
        if ($treeId === null) {
            $treeId = $this->_getTreeId();
        }
        // Определяем параметр $near, будущий левый ключ
        // перемещяемого узла
        $near = $brotherLeftKey -
         1;
        
        return $this->moveNode($leftKey, $rightKey, $level, $brotherLevel, $near,
        $treeId);
    }

    /**
     * Перемещение рядом с другим узлом, ниже
     *
     * @param int $leftKey левый ключ
     * @param int $rightKey правый ключ
     * @param int $level уровень вложенности
     * @param int $brotherRightKey правый ключ узла к которому переносим
     * @param int $brotherLevel уровень вложенности узла к которому переносим
     * @param int $treeId идентификатор дерева
     * @return boolean
     */
    public function movingBrotherDown($leftKey, $rightKey, $level, $brotherRightKey, $brotherLevel,
    $treeId = null)
    {
        if ($treeId === null) {
            $treeId = $this->_getTreeId();
        }
        
        $near = $brotherRightKey;
        
        $treeId = $this->_getTreeId();
        return $this->moveNode($leftKey, $rightKey, $level, $brotherLevel, $near,
        $treeId);
    }
}